/*
 * CMediaCMediaStorageChannel.cpp
 *
 *  Created on: 2018年7月29日
 *      Author: zhengboyuan
 */

#include "CMediaStorageChannel.h"
#include "CLog.h"
#include "TStringUtil.h"
#include "Path.h"
#include "TFileUtil.h"
#include "DateTime.h"
#include "MFormatUtil.h"
#include "TSafeStr.h"
#include "StopWatch.h"


namespace av
{

static size_t rfind(const char* p, size_t length, char sp)
{
    for (size_t pos = length; pos != 0; -- pos)
    {
        if (p[pos - 1] == sp)
        {
            return pos -1;
        }
    }
    return (size_t)-1;
}


std::string CMediaStorageChannel::getName(int id)
{
    return comn::StringCast::toString(id);
}

std::string CMediaStorageChannel::getEventFilePath(const char* recPath, size_t length)
{
    size_t idx = rfind(recPath, length, '.');
    if (idx == std::string::npos)
    {
        idx = length;
    }

    std::string path(recPath, idx);
    path.append(".json");
    return path;
}

std::string CMediaStorageChannel::makeRecordName(int id)
{
    comn::DateTime dt = comn::DateTime::now();
    std::string name = dt.format(nameTemplate.c_str());
    comn::StringUtil::replace(name, '/', '_');
    std::string chl = comn::StringCast::toString(id);
    comn::StringUtil::replace(name, "{c}", chl);
    return name;
}

std::string CMediaStorageChannel::getFileExt(RecordFileFormat fmt)
{
    if (fmt == kMp4File)
    {
        return ".mp4";
    }
    else if (fmt == kTsFile)
    {
        return ".ts";
    }
    else if (fmt == kRawFile)
    {
        return ".raw";
    }
    else if (fmt == kPsFile)
    {
        return ".ps";
    }
    return ".av";
}

bool CMediaStorageChannel::forceAac = false;

bool CMediaStorageChannel::enableSubDir = true;
std::string CMediaStorageChannel::nameTemplate = "{c}_%Y%m%d_%H%M%S";


CMediaStorageChannel::CMediaStorageChannel():
        m_device(),
        m_fileFormat(kRawFile),
        m_fileDuration(DEFAULT_FILE_DURATION),
        m_rootPath(),
        m_recordEvents(),
        m_format(),
        m_record(),
        m_byteCount(),
        m_startTime(),
        m_recordType(),
        m_sink(),
        m_dataStore()
{
    memset(&m_record, 0, sizeof(m_record));
}

CMediaStorageChannel::~CMediaStorageChannel()
{
    close();
}

bool CMediaStorageChannel::open(const char* device)
{
    if (!device || strlen(device) == 0)
    {
        return false;
    }

    m_device = device;

    if (!m_rootPath.empty() && !comn::Path::createDirectories(m_rootPath))
    {
        CLog::error("failed to create channel record dir: %s\n", m_rootPath.c_str());
        return false;
    }

    m_chlThread.setSink(this);

    return true;
}

void CMediaStorageChannel::close()
{
    stopRecord();

    m_chlThread.close();

    m_format.clear();
}

bool CMediaStorageChannel::isOpen()
{
    return m_device.size() > 0;
}

const char* CMediaStorageChannel::getDevice()
{
    return m_device.c_str();
}

void CMediaStorageChannel::setName(const char* name)
{
    if ((name != NULL) && (strlen(name) > 0))
    {
        m_name = name;
    }
    else
    {
        m_name = m_device;
    }
}

const std::string& CMediaStorageChannel::getName()
{
    return m_name;
}


bool CMediaStorageChannel::startRecord(const MFormat& fmt)
{
	CLog::info("CMediaStorageChannel::startRecord.\n");

    if (fmt.vPropSize == 0)
    {
        CLog::warning("MFormat.vprop not set!\n");
    }

    if (fmt.audioCodec == MCODEC_AAC)
    {
        if (fmt.configSize == 0)
        {
            CLog::error("MFormat.config not set!\n");
        }
    }

    av::MFormatUtil::copyTo(&fmt, m_format);
    if (!m_format.isValid())
    {
        return false;
    }

    if (!m_chlThread.isRunning())
    {
        m_chlThread.open();
    }

    m_propDetector.setCodec(m_format.m_codec);
    m_naluJoiner.reset(createNaluJoiner(m_format.m_codec));

    //CLog::info("codec:%d, joiner:%p\n", m_format.m_codec, m_naluJoiner.get());

    m_startTime = time(NULL);

    return true;
}

void CMediaStorageChannel::stopRecord()
{
	CLog::info("CMediaStorageChannel::stopRecord.\n");

    m_startTime = 0;

    closeRecord();
}

int CMediaStorageChannel::writeVideo(uint8_t* data, int size, int64_t pts)
{
	//CLog::info("CMediaStorageChannel::writeVideo. started: %d\n", isStarted());

    if (!isStarted())
    {
        return 0;
    }

    if (!m_writer && !m_naluJoiner)
    {
        return ENOENT;
    }

    bool isKeyFrame = false;
    int naluType = 0;
    if (m_propDetector.detect(data, size, isKeyFrame, naluType))
    {
        m_format.setVideoProp(m_propDetector.getProp().c_str(), m_propDetector.getProp().size());
    }

    int flags = 0;

    flags |= isKeyFrame ? MEDIA_FLAG_KEY : 0;

    MPacket inPkt;
    inPkt.type = MEDIA_TYPE_VIDEO;
    inPkt.data = data;
    inPkt.size = size;
    inPkt.pts = pts;
    inPkt.flags = flags;
    inPkt.duration = 0;

    MPacket outPkt;
    memset(&outPkt, 0, sizeof(outPkt));

    {
        comn::AutoCritSec lock(m_cs);
        if (!m_naluJoiner->write(inPkt, naluType, outPkt))
        {
            CLog::warning("failed to write to nalu joiner. size:%d\n", size);
            return EBADF;
        }
    }

    if (!isVideoPropReady() && (m_fileFormat != av::kPsFile))
    {
        CLog::warning("video prop is not ready. size:%d\n", size);
        return EBADF;
    }

    m_propDetector.detect(outPkt.data, outPkt.size, isKeyFrame, naluType);

    if (!isRecordOpen())
    {
        std::string recordPath = makeRecordPath();
        openRecord(recordPath);
    }
    else
    {
        /// 判断是否要切换
        if (isKeyFrame)
        {
            int duration = getRecordingDuration();
            if (duration > m_fileDuration)
            {
                //int len = std::min(16, outPkt.size);
                //std::string hex = comn::StringCast::toHexGroupString(outPkt.data, len);
                //CLog::info("before switch record. nalu:%02x, size:%d. data:%s\n",
                //  naluType, isKeyFrame, hex.c_str());

                switchRecord();
            }
        }
    }

    //CLog::info("write video. size:%d, pts:%lld, flags:%d\n", outPkt.size, outPkt.pts, flags);
    if (!m_writer->writeVideo(outPkt.data, outPkt.size, outPkt.pts, flags))
    {
        CLog::error("failed to write video. size:%d, flags:%d\n", outPkt.size, flags);
        return EFAULT;
    }

    m_byteCount += outPkt.size;

    return 0;
}

int CMediaStorageChannel::writeVideoEx(uint8_t* data, int size, int64_t pts, int flags)
{
    if (!isStarted())
    {
        return 0;
    }

    bool isKeyFrame = (flags & av::MEDIA_FLAG_KEY) != 0;

    if (!isRecordOpen())
    {
        std::string recordPath = makeRecordPath();
        openRecord(recordPath);
    }
    else
    {
        /// 判断是否要切换
        if (isKeyFrame)
        {
            int duration = getRecordingDuration();
            if (duration > m_fileDuration)
            {
                switchRecord();
            }
        }
    }

    comn::AutoCritSec lock(m_cs);
    //CLog::info("write video. size:%d, pts:%lld, flags:%d\n", outPkt.size, outPkt.pts, flags);
    if (!m_writer || !m_writer->writeVideo(data, size, pts, flags))
    {
        return EFAULT;
    }

    m_byteCount += size;

    return 0;
}

int CMediaStorageChannel::writeAudio(uint8_t* data, int size, int64_t pts)
{
    if (!isStarted())
    {
        return 0;
    }

    comn::AutoCritSec lock(m_cs);

    if (!m_writer)
    {
        if (!m_format.hasVideo() && m_format.hasAudio())
        {
            std::string recordPath = makeRecordPath();
            openRecord(recordPath);

            if (!m_writer)
            {
                return ENOENT;
            }
        }
        else
        {
            return ENOENT;
        }
    }

    int flags = 0;
    //CLog::info("write Audio. size:%d, pts:%lld, flags:%d\n", size, pts, flags);
    if (!m_writer->writeAudio(data, size, pts, flags))
    {
        return EFAULT;
    }

    m_byteCount += size;

    return 0;
}

void CMediaStorageChannel::setFileFormat(RecordFileFormat fileFormat)
{
    m_fileFormat = fileFormat;
}

void CMediaStorageChannel::setFileDuration(int seconds)
{
    if (seconds > 0)
    {
        m_fileDuration = seconds;
    }
}

void CMediaStorageChannel::setRecordType(int recordType)
{
    m_recordType = recordType;
}

void CMediaStorageChannel::setRecorder(const char* recorder)
{
    comn::AutoCritSec lock(m_cs);
    m_recorder = recorder;
}

bool CMediaStorageChannel::setRecordDir(const std::string& path)
{
    m_rootPath = path;

    bool done = comn::Path::createDirectories(m_rootPath);
    if (!done)
    {
        CLog::error("failed to create channel record dir. %s\n", m_rootPath.c_str());
    }
    return done;
}

std::string CMediaStorageChannel::getPath()
{
    return m_rootPath;
}

bool CMediaStorageChannel::writeEvent(AlarmEvent& event)
{
    comn::AutoCritSec lock(m_cs);

    m_recordEvents |= event.type;

	return m_dataStore->addEvent(event);
}

std::string CMediaStorageChannel::makeRecordPath()
{
    std::string name = makeFileName();

    if (name.rfind('.') == std::string::npos)
    {
        name += getFileExt(m_fileFormat);
    }

    std::string dirPath(m_rootPath);
    if (m_dirTemplate.size() > 0)
    {
        std::string dirName = makeDirName();
        dirPath = comn::Path::join(m_rootPath, dirName);
    }
    else if (CMediaStorageChannel::enableSubDir)
    {
        dirPath = comn::Path::join(m_rootPath, m_name);
    }

    return comn::Path::join(dirPath, name);
}

void CMediaStorageChannel::setSink(MediaStorageSink* sink)
{
    m_sink = sink;
}

bool CMediaStorageChannel::openRecord(const std::string& recordPath)
{
    comn::AutoCritSec lock(m_cs);

    std::string dirPath = comn::Path::dirname(recordPath);
    if (!comn::Path::createDirectories(dirPath))
    {
        CLog::error("failed to create root dir: %s\n", dirPath.c_str());
    }

    MFormat fmt;
    memset(&fmt, 0, sizeof(fmt));
    MFormatUtil::copyFrom(&fmt, m_format);

    m_writer.reset(av::MediaWriterFactory::create((MediaFileFormat)m_fileFormat), av::MediaWriterFactory::destroy);
    if (!m_writer)
    {
        CLog::error("failed to create media writer. fmt:%d\n", m_fileFormat);
        return false;
    }

    if (CMediaStorageChannel::forceAac)
    {
        m_writer->setOutputAudioCodec(MEDIA_CODEC_AAC);
    }

    m_writer->setListener(this);

    if (!m_writer->open(recordPath.c_str(), fmt))
    {
        CLog::error("failed to open writer. fmt:%d, path:%s\n", m_fileFormat, recordPath.c_str());
        return false;
    }

    m_recordPath = recordPath;

    memset(&m_record, 0, sizeof(m_record));
    comn::copyStr(m_record.device, m_device);
    m_record.duration = 0;
    m_record.startTime = time(NULL);
    m_record.stopTime = 0;
    m_record.type = m_recordType;
    comn::copyStr(m_record.filename, comn::Path::basename(recordPath));
    comn::copyStr(m_record.filepath, recordPath);
    m_record.filesize = 0;
    comn::copyStr(m_record.url, recordPath);
    comn::copyStr(m_record.recorder, m_recorder);

    //m_dataStore->addRecord(m_record);
    m_chlThread.postRecord(m_record, true);

    ///
    StorageEvent storageEvent;
    memset(&storageEvent, 0, sizeof(storageEvent));
    comn::copyStr(storageEvent.device, m_device);
    storageEvent.time = time(NULL);
    storageEvent.type = EVENT_RECORD_START;
    storageEvent.data.record = m_record;
    //fireStorageEvent(storageEvent);
    m_chlThread.postStorageEvent(storageEvent);

    return true;
}

void CMediaStorageChannel::closeRecord()
{
    if (!isRecordOpen())
    {
        return;
    }

    ///
    {
        comn::AutoCritSec lock(m_cs);

        if (m_writer)
        {
            MediaWriterPtr writer;
            std::swap(m_writer, writer);
            m_chlThread.postWriter(writer);
        }
    }

    m_record.stopTime = time(NULL);
    m_record.duration = (int)(m_record.stopTime - m_record.startTime);
    m_record.filesize = m_byteCount;
    m_record.events = m_recordEvents;

    //m_dataStore->updateRecord(m_record);
    m_chlThread.postRecord(m_record, false);

    ///
    StorageEvent storageEvent;
    comn::copyStr(storageEvent.device, m_device);
    storageEvent.time = time(NULL);
    storageEvent.type = EVENT_RECORD_STOP;
    storageEvent.data.record = m_record;
    //fireStorageEvent(storageEvent);
    m_chlThread.postStorageEvent(storageEvent);

    comn::AutoCritSec lock(m_cs);
    m_record = MRecord();
    m_recordPath.clear();

    m_recordEvents = 0;

    m_byteCount = 0;
}

bool CMediaStorageChannel::isRecordOpen()
{
    comn::AutoCritSec lock(m_cs);
    return !m_recordPath.empty();
}

void CMediaStorageChannel::switchRecord()
{
    CLog::debug("------------------- switchRecord\n");

    closeRecord();

    std::string recordPath = makeRecordPath();

    /// switch
    StorageEvent storageEvent;
    comn::copyStr(storageEvent.device, m_device);
    storageEvent.time = time(NULL);
    storageEvent.type = EVENT_RECORD_SWITCH;
    storageEvent.data.record = m_record;
    //fireStorageEvent(storageEvent);
    m_chlThread.postStorageEvent(storageEvent);

    //
    openRecord(recordPath);

    CLog::debug("------------------- switchRecord end\n");
}


void CMediaStorageChannel::fireStorageEvent(const StorageEvent& event)
{
    if (m_sink)
    {
        m_sink->onStorageEvent(event);
    }
}

void CMediaStorageChannel::setDataStore(DataStorePtr& dataStore)
{
    m_dataStore = dataStore;

    m_chlThread.setDataStore(m_dataStore);
}

bool CMediaStorageChannel::isVideoPropReady()
{
    return m_format.m_videoProp.size() > 0;
}

int CMediaStorageChannel::getRecordingDuration()
{
    time_t t = time(NULL);
    return (int)(t - m_record.startTime);
}

void CMediaStorageChannel::onStorageEvent(const StorageEvent& event)
{
    fireStorageEvent(event);
}

void CMediaStorageChannel::onWriteStatus(MediaWriter* writer, int status)
{
    int event = status;
    if (status == MEDIA_WRITER_IO_ERROR)
    {
        event = EVENT_RECORD_IO_FAULT;
    }

    StorageEvent storageEvent;
    memset(&storageEvent, 0, sizeof(storageEvent));
    comn::copyStr(storageEvent.device, m_device);
    storageEvent.time = time(NULL);
    storageEvent.type = event;
    storageEvent.data.record = m_record;
    //fireStorageEvent(storageEvent);
    m_chlThread.postStorageEvent(storageEvent);
}

bool CMediaStorageChannel::isStarted()
{
    comn::AutoCritSec lock(m_cs);
    return m_startTime > 0;
}

bool CMediaStorageChannel::setRecordNameTemplate(const char* tmpl)
{
    if (tmpl)
    {
        m_nameTemplate = tmpl;
    }
    else
    {
        m_nameTemplate.clear();
    }
    return true;
}

bool CMediaStorageChannel::setDirTemplate(const char* tmpl)
{
    if (tmpl)
    {
        m_dirTemplate = tmpl;
    }
    else
    {
        m_dirTemplate.clear();
    }
    return true;
}


std::string CMediaStorageChannel::makeDirName()
{
    comn::DateTime dt = comn::DateTime::now();
    std::string name = dt.format(m_dirTemplate.c_str());
    comn::StringUtil::replace(name, '/', '_');

    comn::StringUtil::replace(name, "{c}", m_device);
    comn::StringUtil::replace(name, "{d}", m_device);
    comn::StringUtil::replace(name, "{n}", m_name);
    return name;
}

std::string CMediaStorageChannel::makeFileName()
{
    if (m_nameTemplate.size() > 0)
    {
        return makeFileName(m_nameTemplate);
    }
    else
    {
        return makeFileName(nameTemplate);
    }
}

std::string CMediaStorageChannel::makeFileName(const std::string& templ)
{
    comn::DateTime dt = comn::DateTime::now();
    std::string name = dt.format(templ.c_str());
    comn::StringUtil::replace(name, '/', '_');

    comn::StringUtil::replace(name, "{c}", m_device);
    comn::StringUtil::replace(name, "{d}", m_device);

    comn::StringUtil::replace(name, "{n}", m_name);

    for (size_t i = 0; i < m_varMap.size(); i++)
    {
        std::string key;
        std::string value;
        m_varMap.getAt(i, key, value);

        comn::StringUtil::replace(name, key, value);
    }

    return name;
}

void CMediaStorageChannel::setRecordNameVar(char charHolder, const char* value)
{
    std::string name;
    name += '{';
    name += charHolder;
    name += '}';

    if (value == NULL || (strlen(value) == 0))
    {
        m_varMap.remove(name);
    }
    else
    {
        m_varMap.put(name, value);
    }
}




} /* namespace av */

